<?php

interface UHU_Collection extends Iterator,Countable,ArrayAccess
{
/**
*	@param mixed The value to look for in the collection
*	@return mixed FALSE if the value does not exist in the collection, otherwise the offset for the value is returned.
*/
	public function valueExists($val);
}

interface UHU_Class_Collection extends UHU_Collection
{
/**
*	@return ReflectionClass The ReflectionClass object for the class the collection is meant to store
*/
	public function Reflector();
}

interface UHU_Class_collection_static extends UHU_Collection
{
	public static function getReflector();
}

abstract class UHU_Collection_Basic implements UHU_Collection
{
	protected $data = array();
/**
*	@return integer The number of items in the collection
*	@uses uhu_collection_basic::$collection
*/
	public function count()
	{
		return count($this->data);
	}
/**
*	@return mixed The curren item in the collection
*	@uses uhu_collection_basic::$collection
*/
	public function current()
	{
		return current($this->data);
	}
/**
*	@return mixed The key for the curren item in the collection
*	@uses uhu_collection_basic::$collection
*/
	public function key()
	{
		return key($this->data);
	}
/**
*	@return mixed Advances the array pointer for the collection
*	@uses uhu_collection_basic::$collection
*/
	public function next()
	{
		return next($this->data);
	}
/**
*	@return mixed resets the array pointer for the collection
*	@uses uhu_collection_basic::$collection
*/
	public function rewind()
	{
		return reset($this->data);
	}
/**
*	@return bool Returns TRUE if there is an item on the current array pointer
*	@uses uhu_collection_basic::$collection
*/
	public function valid()
	{
		return (bool)$this->current();
	}
/**
*	@param mixed The value to look for in the collection
*	@return mixed FALSE if the value does not exist in the collection, otherwise the offset for the value is returned.
*/
	public function valueExists($val)
	{
		return array_search($val,$this->data);
	}
	public function offsetExists($offset)
	{
		return isset($this->data[$offset]);
	}
	public function offsetUnset($offset)
	{
		unset($this->data[$offset]);
	}
	public function offsetGet($offset)
	{
		return isset($this->data[$offset]) ? $this->data[$offset] : null;
	}
}

abstract class UHU_Class_Collection_Basic extends UHU_Collection_Basic
{
	public function offsetSet($offset,$val)
	{
		if(is_object($val) === false)
		{
			throw new InvalidArgumentException('Value must be an instance of a class.');
		}
		else if($this->Reflector()->isInstance($val) === false)
		{
			$foo = new ReflectionClass($val);
			if($foo->isSubclassOf($this->Reflector()) === false)
			{
				throw new InvalidArgumentException('Values in ' . get_class($this) . ' (an instance of ' . get_class($val) . ') must be a sub-class of ' . $this->Reflector()->getName());
			}
		}
		if($offset === null)
		{
			$offset = $this->count();
		}
		$this->data[$offset] = $val;
	}
}

abstract class UHU_Static_Collection extends UHU_Class_Collection_Basic implements UHU_Class_collection_static
{
	public function Reflector()
	{
		return call_user_func(array(get_class($this),'getReflector'));
	}
}

class UHU_Class_Collection_Auto extends UHU_Class_Collection_Basic
{
	protected $reflector;
	public function __construct(object $val,$autoadd=true)
	{
		$this->reflector = new ReflectionClass(get_class($val));
		if($autoadd === true)
		{
			$this[] = $val;
		}
	}
	public function Reflector()
	{
		if(isset($this->reflector) === false)
		{
			throw new RuntimeException('Reflector has not yet been set. Reflector is set during instantiation.');
		}
	}
}


class UHU_Collection_Callbacks extends UHU_Collection_Basic
{
	protected $callback_parameters = array();
	protected $paranoid = false;
	public function __construct()
	{
		if(func_num_args() < 1)
		{
			throw new RuntimeException(__CLASS__ . ' must be instantiated with 1 or more arguments.');
		}
		else
		{
			$args = func_get_args();
			self::_validate_arguments($args);
			foreach($args as $arg)
			{
				$this->callback_parameters[] = $arg;
			}
		}
	}
	protected static function _validate_arguments(array $args)
	{
		foreach($args as $arg)
		{
			if(is_object($arg) === false)
			{
				throw new InvalidArgumentException(__CLASS__ . ' must be instantiated with object arguments only.');
			}
		}
	}
	protected function _compare_arguments(array $args)
	{
		foreach($args as $k=>$arg)
		{
			$compare_against = get_class($this->callback_parameters[$k]);
			if(($arg instanceof ReflectionClass) === false)
			{
				throw new InvalidArgumentException('Only instances of ReflectionClass should be passed here.');
			}
			else if($arg->getName() !== $compare_against && $arg->isSubclassOf($compare_against) === false)
			{
				throw new InvalidArgumentException('Parameter ' . $k . ' must be an instance or sub-class of \'' . $compare_against . '\'.');
			}
		}
	}
	public function offsetSet($offset,$value)
	{
		if(is_callable($value) === false)
		{
			throw new InvalidArgumentException('Values appended to ' . __CLASS__ . ' must be valid callbacks.');
		}
		else if($this->is_paranoid() === true)
		{
			if(is_array($value) || strpos($value,'::') !== false)
			{
				if(is_array($value) === true)
				{
					if(is_object(current($value)) === true)
					{
						$class_Reflector = new ReflectionObject(current($value));
					}
					else
					{
						$class_Reflector = new ReflectionClass(current($value));
					}
					$method = next($value);
				}
				else
				{
					list($class,$method) = explode('::',$value);
					$class_Reflector = new ReflectionClass($class);
				}
				$method_Reflector = $class_Reflector->getMethod($method);
				$num_callback_parameters = count($this->callback_parameters);
				if($method_Reflector->getNumberOfParameters() < $num_callback_parameters)
				{
					throw new InvalidArgumentException('Callbacks passed to this instance of ' . __CLASS__ . ' must not have less than ' . $num_callback_parameters . ' parameters.');
				}
				else if($method->getNumberOfRequiredParameters() > $num_callback_parameters)
				{
					throw new InvalidArgumentException('Callbacks passed to this instance of ' . __CLASS__ . ' must not have more than ' . $num_callback_parameters . ' required parameters.');
				}
			}
			else
			{
				$method_Reflector = new ReflectionFunction($value);
			}
			$param_class = array();
			$optional_found = false;
			foreach($method_Reflector->getParameters() as $k=>$param)
			{
				try
				{
					if($param->isOptional() === true)
					{
						if($optional_found === false)
						{
							$optional_found = true;
						}
					}
					$param_class[] = $param->getClass();
					if(end($param_class) === null && $optional_found === false)
					{
						throw new InvalidArgumentException('Hinted type in parameter ' . $k . ' is not a class.');
					}
					else if(end($param_class) !== null && $optional_found === true)
					{
						throw new InvalidArgumentException('Cannot have optional class arguments after optional non-class arguments.');
					}
					if(end($param_class) === null)
					{
						unset($param_class[$k]);
					}
				}
				catch(ReflectionException $e)
				{
					if(preg_match('/^Class .+ does not exist$/S',$e->getMessage()) === 1)
					{
						throw new InvalidArgumentException('Hinted type in parameter ' . $k . ' does not exist.');
					}
					else
					{
						throw $e;
					}
				}
			}
			$this->_compare_arguments($param_class);
		}
		if($offset === null)
		{
			$offset = $this->count();
		}
		$this->data[$offset] = $value;
	}
	public function doCallbacks()
	{
		foreach($this as $callback)
		{
			call_user_func_array($callback,$this->callback_parameters);
		}
	}
	public function make_paranoid()
	{
		$this->paranoid = true;
	}
	public function is_paranoid()
	{
		return (bool)$this->paranoid;
	}
}

class UHU_Collection_Callbacks_RunOnce extends UHU_Collection_Callbacks
{
	protected $ran = false;
	public function doCallbacks()
	{
		if($this->ran === false)
		{
			parent::doCallbacks();
			$this->ran = true;
		}
		else
		{
			throw new RuntimeException('Callbacks held in an instance of ' . __CLASS__ . ' should only be run once per script execution.');
		}
	}
}

?>